package fun.tan90.easy.binlog.manager.service.impl;


import cn.hutool.core.util.RandomUtil;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryCondition;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.util.UpdateEntity;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import fun.tan90.easy.binlog.base.convention.Res;
import fun.tan90.easy.binlog.base.convention.enums.IErrorCode;
import fun.tan90.easy.binlog.base.convention.exception.ClientException;
import fun.tan90.easy.binlog.base.convention.page.BasePageQuery;
import fun.tan90.easy.binlog.base.convention.page.SimplePage;
import fun.tan90.easy.binlog.base.model.OpenBinlogClientReq;
import fun.tan90.easy.binlog.base.model.OpenBinlogClientRes;
import fun.tan90.easy.binlog.manager.dataobject.BinlogMaster;
import fun.tan90.easy.binlog.manager.dataobject.enums.SlaveClientStatusEnum;
import fun.tan90.easy.binlog.manager.dataobject.mapper.BinlogMasterMapper;
import fun.tan90.easy.binlog.manager.dataobject.mapper.BinlogOutputRuleMapper;
import fun.tan90.easy.binlog.manager.dataobject.table.BinlogMasterTableDef;
import fun.tan90.easy.binlog.manager.dataobject.table.BinlogOutputRuleTableDef;
import fun.tan90.easy.binlog.manager.model.cmd.BinlogMasterAddCmd;
import fun.tan90.easy.binlog.manager.model.cmd.BinlogMasterPageCmd;
import fun.tan90.easy.binlog.manager.model.cmd.BinlogMasterUpdateCmd;
import fun.tan90.easy.binlog.manager.model.convert.BinlogMasterConvert;
import fun.tan90.easy.binlog.manager.model.vo.BinlogMasterInfoVo;
import fun.tan90.easy.binlog.manager.model.vo.BinlogMasterPageVo;
import fun.tan90.easy.binlog.manager.server.EbManager;
import fun.tan90.easy.binlog.manager.service.BinlogMasterService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.List;

import static com.mybatisflex.core.query.QueryMethods.noCondition;

/**
 * 服务层实现。
 *
 * @author 陈浩杰
 * @since 2023-09-01
 */
@Slf4j
@Service
public class BinlogMasterServiceImpl extends ServiceImpl<BinlogMasterMapper, BinlogMaster> implements BinlogMasterService {

    @Resource
    BinlogMasterConvert binlogMasterConvert;

    @Resource
    BinlogOutputRuleMapper binlogOutputRuleMapper;

    @Resource
    EbManager ebManager;

    @Override
    public String addBinlogMaster(BinlogMasterAddCmd addCmd) {
        String masterName = addCmd.getMasterName();
        long count = mapper.selectCountByQuery(QueryWrapper.create().where(BinlogMasterTableDef.BINLOG_MASTER.MASTER_NAME.eq(masterName)));
        if (count > 0) {
            throw new ClientException(IErrorCode.BINLOG_1004001, masterName);
        }

        String masterCode = RandomUtil.randomString(RandomUtil.BASE_CHAR, 8);
        long checkMasterCode = 1;
        while (checkMasterCode > 0) {
            checkMasterCode = mapper.selectCountByQuery(QueryWrapper.create().where(BinlogMasterTableDef.BINLOG_MASTER.MASTER_CODE.eq(masterCode)));
            if (checkMasterCode == 0) {
                break;
            }
            masterCode = RandomUtil.randomString(RandomUtil.BASE_CHAR, 8);
        }

        BinlogMaster binlogMaster = binlogMasterConvert.addCmdToDo(addCmd);
        binlogMaster.setMasterCode(masterCode);
        binlogMaster.setSlaveClientStatus("0");
        mapper.insert(binlogMaster);
        return binlogMaster.getId();
    }

    @Override
    public boolean updateBinlogMaster(BinlogMasterUpdateCmd updateCmd) {
        String masterId = updateCmd.getId();
        String masterName = updateCmd.getMasterName();
        long count = mapper.selectCountByQuery(QueryWrapper.create().where(BinlogMasterTableDef.BINLOG_MASTER.MASTER_NAME.eq(masterName)).and(BinlogMasterTableDef.BINLOG_MASTER.ID.ne(masterId)));
        if (count > 0) {
            throw new ClientException(IErrorCode.BINLOG_1004001, masterName);
        }
        BinlogMaster binlogMaster = binlogMasterConvert.updateCmdToDo(updateCmd);
        return mapper.update(binlogMaster) > 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean delBinlogMaster(String id) {
        int rows = mapper.deleteById(id);
        if (rows > 0) {
            int delRows = binlogOutputRuleMapper.deleteByCondition(QueryCondition.create(BinlogOutputRuleTableDef.BINLOG_OUTPUT_RULE.MASTER_ID, id));
            log.info("{} 输出规则删除完成,条数 {}", id, delRows);
        }
        return rows > 0;
    }

    @Override
    public BinlogMasterInfoVo info(String id) {
        BinlogMaster binlogMaster = mapper.selectOneById(id);
        return binlogMasterConvert.doToInfoVo(binlogMaster);
    }

    @Override
    public Res<SimplePage<BinlogMasterPageVo>> paging(BinlogMasterPageCmd pageCmd) {
        String slaveWorkId = pageCmd.getSlaveWorkId();
        String masterName = pageCmd.getMasterName();
        String hostname = pageCmd.getHostname();

        QueryWrapper queryWrapper = QueryWrapper.create()
                .where(StringUtils.hasLength(masterName) ? BinlogMasterTableDef.BINLOG_MASTER.MASTER_NAME.like(masterName) : noCondition())
                .and(StringUtils.hasLength(slaveWorkId) ? BinlogMasterTableDef.BINLOG_MASTER.SLAVE_WORK_ID.eq(slaveWorkId) : noCondition())
                .and(StringUtils.hasLength(hostname) ? BinlogMasterTableDef.BINLOG_MASTER.HOSTNAME.like(hostname) : noCondition());

        List<BasePageQuery.OrderColumn> orders = pageCmd.getOrders();
        if (!CollectionUtils.isEmpty(orders)) {
            queryWrapper.orderBy(orders.stream().map(n -> n.getColumn() + " " + (n.isAsc() ? "asc" : "desc")).toArray(String[]::new));
        }
        Page<BinlogMaster> paginate = mapper.paginate(pageCmd.getPageIndex(), pageCmd.getPageSize(), pageCmd.getTotalRow(), queryWrapper);
        return Res.pageOf(paginate.getPageNumber(), paginate.getTotalRow(), binlogMasterConvert.dosToPageVos(paginate.getRecords()));
    }

    @Override
    public String publishBinlogClient(String masterId) {
        BinlogMaster binlogMaster = mapper.selectOneById(masterId);
        OpenBinlogClientReq openBinlogClientReq = binlogMasterConvert.doToReq(binlogMaster);
        OpenBinlogClientRes openBinlogClientRes = ebManager.openBinlogClient(openBinlogClientReq);
        String message = openBinlogClientRes.getMessage();
        if (!"ok".equals(message)) {
            throw new ClientException(IErrorCode.BINLOG_1004011, message);
        }
        String workId = openBinlogClientRes.getWorkId();
        String masterVersion = ebManager.masterVersion(workId, openBinlogClientReq.getMasterCode());
        log.info("binlog client 启动成功 所在节点: {}, 主库版本: {}", workId, masterVersion);
        mapper.updateByCondition(BinlogMaster.builder()
                        .slaveWorkId(workId)
                        .slaveClientStatus(SlaveClientStatusEnum.开启.getKey())
                        .slaveOpenRes(message)
                        .masterVersion(masterVersion)
                        .build(),
                BinlogMasterTableDef.BINLOG_MASTER.ID.eq(masterId)
        );
        return message;
    }

    @Override
    public String closeBinlogClient(String masterId) {
        BinlogMaster binlogMaster = mapper.selectOneById(masterId);
        if (SlaveClientStatusEnum.停止.getKey().equals(binlogMaster.getSlaveClientStatus())) {
            throw new ClientException(IErrorCode.BINLOG_1004012);
        }
        String slaveWorkId = binlogMaster.getSlaveWorkId();
        String masterCode = binlogMaster.getMasterCode();
        boolean closed = ebManager.closeBinlogClient(slaveWorkId, masterCode);
        if (!closed) {
            throw new ClientException(IErrorCode.BINLOG_1004013);
        }
        BinlogMaster updateBinlogMaster = UpdateEntity.of(BinlogMaster.class, masterId);
        updateBinlogMaster.setSlaveWorkId(null);
        updateBinlogMaster.setSlaveOpenRes(null);
        updateBinlogMaster.setSlaveClientStatus(SlaveClientStatusEnum.停止.getKey());
        mapper.update(updateBinlogMaster);
        return "ok";
    }
}
